package middleware

import (
	"errors"
	"fmt"
	"k9-panel/app/adapter/internal"
	"k9-panel/app/model"
	"k9-panel/app/permission"
	"k9-panel/app/response"
	"k9-panel/app/service"
	"k9-panel/app/service/dto"
	"k9-panel/config"
	"log"
	"time"

	jwt "github.com/appleboy/gin-jwt/v2"
	"github.com/gin-gonic/gin"
)

var (
	IdentityKey = "id"
	jwtSecret   string
)

func Init(config *config.Configuration) {
	jwtSecret = config.JwtSecret
}

func NewJwtMiddleware() (*jwt.GinJWTMiddleware, error) {
	return jwt.New(initParams())
}

func RegisterRoute(r *gin.Engine, handle *jwt.GinJWTMiddleware) {
	//middleware init
	r.Use(handlerMiddleware(handle))

	r.POST("/login", handle.LoginHandler)
	r.NoRoute(handle.MiddlewareFunc(), handleNoRoute())

	auth := r.Group("/auth", handle.MiddlewareFunc())
	auth.GET("/refresh_token", handle.RefreshHandler)
	auth.GET("/hello", helloHandler)
}

func handlerMiddleware(authMiddleware *jwt.GinJWTMiddleware) gin.HandlerFunc {
	return func(context *gin.Context) {
		errInit := authMiddleware.MiddlewareInit()
		if errInit != nil {
			log.Fatal("authMiddleware.MiddlewareInit() Error:" + errInit.Error())
		}
	}
}

func initParams() *jwt.GinJWTMiddleware {

	return &jwt.GinJWTMiddleware{
		Realm:       "DE",
		Key:         []byte(jwtSecret),
		Timeout:     time.Hour,
		MaxRefresh:  time.Hour,
		IdentityKey: IdentityKey,
		PayloadFunc: payloadFunc(),

		IdentityHandler: identityHandler(),
		Authenticator:   authenticator(),
		Authorizator:    authorizator(),
		Unauthorized:    unauthorized(),
		TokenLookup:     "header: Authorization, query: token, cookie: jwt",
		TokenHeadName:   "Bearer",
		TimeFunc:        time.Now,
		LoginResponse: func(c *gin.Context, code int, token string, expire time.Time) {
			user, _ := c.Get(dto.IdentityKey)
			fmt.Printf("useruser %+v", user)
			c.JSON(200, response.Success(&internal.AuthRsp{Token: token, User: user.(*dto.User)}))
			return
		},
	}
}

func payloadFunc() func(data interface{}) jwt.MapClaims {
	return func(data interface{}) jwt.MapClaims {
		if v, ok := data.(*dto.User); ok {
			return jwt.MapClaims{
				IdentityKey: v.ID,
			}
		}
		return jwt.MapClaims{}
	}
}

func identityHandler() func(c *gin.Context) interface{} {
	return func(c *gin.Context) interface{} {
		claims := jwt.ExtractClaims(c)
		id := claims[IdentityKey]
		u := &dto.User{
			ID: uint(id.(float64)),
		}
		account, err := service.Account.Get(c, u.ID)
		if err != nil {
			panic(err)
		}
		u.Username = account.Username
		u.Super = account.Super
		err = permission.Perm.LoadPolicy()
		if err != nil {
			if err != nil {
				panic(err)
			}
		}
		return u
	}
}

func authenticator() func(c *gin.Context) (interface{}, error) {
	return func(c *gin.Context) (interface{}, error) {
		var loginVals internal.AuthLoginParam
		if err := c.ShouldBind(&loginVals); err != nil {
			return "", jwt.ErrMissingLoginValues
		}
		account, err := service.Account.Query(c, &dto.AccountQueryParam{
			Username: loginVals.Username,
		})
		if err != nil || account == nil {
			panic(errors.New("用户或密码错误！"))
			//return nil, err
		}
		loginAccount := model.Account{
			Username: loginVals.Username,
			Password: loginVals.Password,
			Salt:     account.Salt,
		}
		same, _ := loginAccount.Same(account.Password)
		if same {
			d := &dto.User{
				Username: loginVals.Username,
				ID:       account.ID,
				Super:    account.Super,
			}
			c.Set(dto.IdentityKey, d)

			return d, nil
		}
		return nil, jwt.ErrFailedAuthentication
	}
}

func authorizator() func(data interface{}, c *gin.Context) bool {
	return func(data interface{}, c *gin.Context) bool {
		if v, ok := data.(*dto.User); ok && v.Username != "" {
			return true
		}
		return false
	}
}

func unauthorized() func(c *gin.Context, code int, message string) {
	return func(c *gin.Context, code int, message string) {
		c.JSON(200, response.Fail[string](message))
	}
}

func handleNoRoute() func(c *gin.Context) {
	return func(c *gin.Context) {
		claims := jwt.ExtractClaims(c)
		log.Printf("NoRoute claims: %#v\n", claims)
		c.JSON(404, gin.H{"code": "PAGE_NOT_FOUND", "message": "Page not found"})
	}
}

func helloHandler(c *gin.Context) {
	claims := jwt.ExtractClaims(c)
	user, _ := c.Get(IdentityKey)
	c.JSON(200, gin.H{
		"userID":   claims[IdentityKey],
		"userName": user.(*dto.User).Username,
		"text":     "Hello World.",
	})
}
